package com.dilean.impl.model;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import com.dilean.api.dao.PersonDAO;
import com.dilean.api.model.Attribute;
import com.dilean.api.model.AttributeValue;
import com.dilean.api.model.Conversation;
import com.dilean.api.model.Person;
import com.dilean.spring.AplicationContextHolder;

@Entity
@Table(name = "PERSONS")
public class PersonImpl extends AbstractEntityImpl implements Person {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DILEAN_ID_SEQ")
    @SequenceGenerator(name = "DILEAN_ID_SEQ", sequenceName = "DILEAN_ID_SEQ", allocationSize = 1)
    @Column(name = "ID")
    private Integer id;

    @Column(name = "NAME", unique = false, nullable = false)
    private String name;

    @Override
    public Integer getId() {
        return id;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @ManyToMany(targetEntity = PersonImpl.class, fetch = FetchType.LAZY)
    @JoinTable(name = "FRIENDS",
            joinColumns = @JoinColumn(name = "ID"),
            inverseJoinColumns = @JoinColumn(name = "FRIEND_ID"))
    private Collection<Person> friends = new HashSet<Person>();

    @ManyToMany(targetEntity = PersonImpl.class, mappedBy = "friends")
    private Collection<Person> requests = new HashSet<Person>();

    @ManyToMany(targetEntity = ConversationImpl.class)
    @JoinTable(name = "PERS_CONVERSATIONS",
            joinColumns = @JoinColumn(name = "PERSON_ID"),
            inverseJoinColumns = @JoinColumn(name = "CONVERSATION_ID"))
    private Collection<Conversation> conversations = new HashSet<Conversation>();
    
    


    // All methods that works with the map need to be synchronized 
    private transient Map<Integer, AttributeValue<?>> parameters = new HashMap<Integer, AttributeValue<?>>();

    // All methods that works with the set need to be synchronized 
    private transient Map<Attribute, AttributeValue<?>> updatedParams = new HashMap<Attribute, AttributeValue<?>>();

    private transient PersonDAO personDAO;

    private PersonDAO getPersonDAO() {
        if (personDAO != null) {
            return personDAO;
        }

        synchronized (this) {
            if (personDAO != null) {
                return personDAO;
            }

            personDAO = AplicationContextHolder.getInstance().getContext().getBean(PersonDAO.class);
        }

        return personDAO;
    }

    @Override
    public Collection<Person> getFriends() {
        return friends;
    }

    @Override
    public void setFriends(Collection<Person> friends) {
        this.friends = friends;
    }

    @Override
    public Collection<Person> getFriendRequests() {
        return requests;
    }

    @Override
    public void setFriendRequests(Collection<Person> requests) {
        this.requests = requests;
    }

    @Override
    public Collection<Conversation> getConversations() {
        return conversations;
    }

    public void setConversations(Collection<Conversation> conversations) {
        this.conversations = conversations;
    }

    @Override
    public void addConversation(Conversation conversation) {
        conversations.add(conversation);
    }

    @Override
    public void addToFriends(Person person) {
        friends.add(person);
    }

     @Override
    public void deliteFriend(Person person) {
    friends.remove(person);
}
    
    /**
     * Get attributeValue of given class. Returns null if no value were found.
     * The method uses internal cache.
     */
    @SuppressWarnings("unchecked")
    @Override
    public synchronized <T> T getAttributeValue(Integer attributeId, Class<T> t) {
        if (parameters.containsKey(attributeId)) {
            return (T) parameters.get(attributeId);
        }

        final T value = (T) getPersonDAO().getParameter(getId(), attributeId);

        parameters.put(attributeId, (AttributeValue<?>) value);
        return value;
    }

    /**
     * Returns <b>read-only</b> copy of parameter values
     */
    @Override
    public synchronized <T> Collection<AttributeValue<?>> getParameters() {
        // DOTO get all the parameters by one select and add then into parameters-map
        Collection<AttributeValue<T>> collection = getPersonDAO().getParameters(getId());
        for (AttributeValue<T> attrValue : collection) {
            parameters.put(attrValue.getAttribute().getId(), attrValue);
        }
        return Collections.unmodifiableCollection(parameters.values());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeValue<T> setParameter(Integer attrId, AttributeValue<?> attrValue) {
        return (AttributeValue<T>) parameters.put(attrId, attrValue);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeValue<T> setParameterForUpdate(Integer attrId, AttributeValue<?> attrValue) {
        updatedParams.put(attrValue.getAttribute(), attrValue);
        return (AttributeValue<T>) parameters.put(attrId, attrValue);
    }

    /**
     * Returns read-only collection of attr-ids to update
     */
    @Override
    public Map<Attribute, AttributeValue<?>> getAttrsToUpdate() {

        return Collections.unmodifiableMap(updatedParams);
    }

    @Override
    public void clearAttrsToUpdate() {
        updatedParams.clear();
    }

    
    

}
